// TickerWindow.cpp: implementation of the CTickerWindow class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "TickerWindow.h"
#include <assert.h>
#include "TracerDll.h"


///////////////////////////////////////////////////////////////////////
// CTickerItem
///////////////////////////////////////////////////////////////////////
CTickerItem::CTickerItem (INewsContentProvider* pvdr, NewsItem* news)
{
	assert (pvdr && news);
	m_pNext = NULL;
	m_pvdr = pvdr;

    m_pNews = (NewsItem*) malloc(sizeof (NewsItem));
    DBG_NewMem (m_pNews, "CTickerItem::CTickerItem, m_pNews malloced.");

	CopyNewsItem (m_pNews, news);
	
    m_nFirstVisiblePos = 0;
	m_nScreenPos = 0;
	m_nTextWidth = 0;
    m_brBk = CreateSolidBrush(m_pNews->bkgrd);
}

CTickerItem::~CTickerItem()
{
	assert (m_pvdr && m_pNews);
    if (m_pNews->text) {
        free (m_pNews->text);
        DBG_DelMem (m_pNews->text, "CTickerItem::~CTickerItem m_pNews->text freed.");
    }
    free (m_pNews);
    DBG_DelMem (m_pNews, "CTickerItem::~CTickerItem m_pNews freed.");
    if (m_brBk) {
        DeleteObject(m_brBk);
    }
}

CTickerItem* CTickerItem::GetNext()
{
	return m_pNext;
}

void CTickerItem::SetNext(CTickerItem* next)
{
	m_pNext = next;
}

void CTickerItem::CopyNewsItem(NewsItem* dest, NewsItem* src)
{
   assert (dest && src);
   *dest = *src;
   dest->text = (char*) malloc(strlen(src->text)+1);
   DBG_NewMem (dest->text, "CTickerItem::CopyNewsItem dest->text malloced.");

   if (dest->text) {
      strcpy (dest->text, src->text);
   }
}

///////////////////////////////////////////////////////////////////////
// CTickerItemList
///////////////////////////////////////////////////////////////////////
CTickerItemList::CTickerItemList()
{
	m_pHead = m_pTail = NULL;
}

CTickerItemList::~CTickerItemList()
{
	CTickerItem* pItem = m_pHead;

	while (pItem) {
		CTickerItem* next = pItem->GetNext();
		delete pItem;
        DBG_DelMem (pItem, "CTickerItemList::~CTickerItemList, pItem deleted.");

		pItem = next;
	}
}

void CTickerItemList::AddToTail(CTickerItem* pTail)
{
	assert ((!m_pHead && !m_pTail) || (m_pHead && m_pTail));
	assert (pTail && !(pTail->GetNext()));

	if (!m_pTail) {
		// This list is empty
		m_pHead = m_pTail = pTail;
		return;
	}

	m_pTail->SetNext(pTail);
	m_pTail= pTail;
}

int CTickerItemList::RemoveHead()
{
	if (!m_pHead) {
		assert (!m_pTail);
		return -1;
	}

	assert (m_pTail);
	CTickerItem* pHead = m_pHead;
	
	m_pHead = m_pHead->GetNext();
	if (!m_pHead) {
		m_pTail = NULL;
	}
	delete (pHead);
    DBG_DelMem (pHead, "CTickerItemList::RemoveHead, pHead deleted.");

	return 0;
}

CTickerItem* CTickerItemList::GetHead()
{
	return m_pHead;
}

#define DEFAULT_LEADING 50
#define DEFAULT_MOVE_STEP 2
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTickerWindow::CTickerWindow()
{
	m_nTickTimerID = 0;
	m_nMoveTimerID = 0;
	m_nCurProvider = 0;
	m_nMoveStep = DEFAULT_MOVE_STEP;
	m_nLeading = m_nTrailing = DEFAULT_LEADING;

	int i;
	for (i = 0; i < MAX_PROVIDERS; i++) {
		m_providers[i] = NULL;
	}
	return;
}

CTickerWindow::~CTickerWindow()
{
	return;
}

#define MOVE_INTERVAL 100 // 10 milliseconds

LRESULT CTickerWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	m_nTickTimerID = SetTimer(TICKTIMERID, TICK_INTERVAL);
	m_nMoveTimerID = SetTimer(MOVETIMERID,  MOVE_INTERVAL);
	return 0;
}

LRESULT CTickerWindow::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	if (m_nTickTimerID) {
		KillTimer (m_nTickTimerID);
	}
	if (m_nMoveTimerID) {
		KillTimer (m_nMoveTimerID);
	}
	
#ifdef _TICKER_TEST_
	PostQuitMessage(0);
#endif // _TICKER_TEST_

	return 0;
}

#define SAMPLE_TEXT "Hello"

LRESULT CTickerWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	PAINTSTRUCT ps;
	BeginPaint (&ps);
	
	HDC hdc = ps.hdc;

	RECT rc;
	GetClientRect(&rc);
	UINT nScrnWidth = rc.right - rc.left;
	PrepareContent (hdc, nScrnWidth);
	
	SetTextAlign(hdc, TA_LEFT|TA_TOP);
	DrawContent(hdc);

	EndPaint (&ps);
	return 0;
}

LRESULT CTickerWindow::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	return 0;
}

LRESULT CTickerWindow::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	if (wParam == m_nTickTimerID) {
		CallTickForAllProviders();
		RemoveTimerMsg(m_nTickTimerID);
	} else if (wParam == m_nMoveTimerID) {
		if (!CursorInWindow()) {
			MoveTicker();
		}
		RemoveTimerMsg(m_nMoveTimerID);
	}
	return 0;
}

LRESULT CTickerWindow::OnDblClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   size_t url_size = m_lstItem.GetHead()->m_pvdr->GetURLSize();
   char* url = (char*) malloc(url_size);
   DBG_NewMem (url, "CTickerWindow::OnDblClick, url malloced.");

   if (!url) {
      return -1;
   }
   m_lstItem.GetHead()->m_pvdr->GetURL(url, url_size);

   ShellExecute(m_hWnd, "Open", url, NULL, NULL, SW_SHOWDEFAULT);
   free (url);
   DBG_DelMem (url, "CTickerWindow::OnDblClick, url freed.");
   return 0;
}


void CTickerWindow::CallTickForAllProviders()
{
	int i;
	for ( i = 0; i < MAX_PROVIDERS; i++) {
		if (!m_providers[i]) {
			continue;
		}
		m_providers[i]->Tick();
	}
}

void CTickerWindow::RemoveTimerMsg(UINT nTimerID)
{
/*	MSG msg;

	while (PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE)) {
		if (msg.message != WM_TIMER || msg.hwnd != m_hWnd || msg.wParam != nTimerID) {
			::PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam);
		}
	}*/
	return;
}

void CTickerWindow::MoveTicker()
{
	Invalidate(false);
}

int CTickerWindow::GetNextItem(NewsItem** ppItem)
{
	assert (ppItem);
	*ppItem = NULL;
	int nNextProvider;
	
	if (!m_providers[m_nCurProvider]) {
		m_nCurProvider = FindNextProvider(m_nCurProvider);
		if (m_nCurProvider < 0) {
			// not found.
			return -1;
		}
	}

	int nTries = 0;
	while (!(*ppItem) && nTries < MAX_PROVIDERS) {
		m_providers[m_nCurProvider]->GetNextNewsItem(ppItem);
		if (!(*ppItem)) {
			nNextProvider = FindNextProvider(m_nCurProvider);
			if (nNextProvider < 0) {
				// not found.
				return -1;
			}
			m_nCurProvider = (unsigned int) nNextProvider;
			nTries ++;
		} else {
			return 0;
		}
	}
	
	return -1;
}

int CTickerWindow::FindNextProvider (UINT s)
{
	int nTries = 0;
	while (nTries < MAX_PROVIDERS) {
		s++;
		if (s >= MAX_PROVIDERS) {
			s = 0;
		}
		if (m_providers[s]) {
			return s;
		}
		nTries ++;
	}
	return -1;
}


int CTickerWindow::AddProvider (INewsContentProvider* pvdr)
{
	int nFirstEmpty = -1;

	int i;
	for (i = 0; i < MAX_PROVIDERS; i++) {
		if (!m_providers[i] && nFirstEmpty < 0) {
			nFirstEmpty = i;
			break;
		}
		if (m_providers[i] == pvdr) {
			// this provider is already added.
			return 0;
		}
	}

	if (nFirstEmpty < 0) {
		// provider overflows.
		return -1;
	}
	m_providers[i] = pvdr;
	return 0;
}

void CTickerWindow::RemoveProvider (INewsContentProvider* pvdr)
{
	int i;
	for (i = 0; i < MAX_PROVIDERS; i++) {
		if (m_providers[i] == pvdr) {
			m_providers[i] = NULL;
			return;
		}
	}

	return;
}

// SetMoveStep returns the old Step value, if nStepPixels < = 0, only return
//  the old step value;
unsigned int CTickerWindow::SetMoveStep (unsigned int nStepPixels)
{
	unsigned int old_value = m_nMoveStep;
	if (nStepPixels != 0) {
		m_nMoveStep = nStepPixels;
	}
	return old_value;
}

void CTickerWindow::PrepareContent (HDC hdc, UINT nScrnWidth)
{
	UINT nCalcWidth = 0;

// Calculating the already fetched items.
	CTickerItem* pItem = m_lstItem.GetHead();
	while (pItem) {
		// CalcDisplayPos may remove list head, so pNext
		//  must be fetched before calling CalcDisplayPos
		CTickerItem* pNext =  pItem->GetNext();
		nCalcWidth = CalcDisplayPos(hdc, nCalcWidth, pItem);
		pItem = pNext;
	}
// If there is still room for other news
	while (nCalcWidth < nScrnWidth) {
		NewsItem* pNews = NULL;
		if (GetNextItem(&pNews) < 0) {
			// no items available.
			break;
		}

		assert (pNews);
		CTickerItem* pItem = new CTickerItem(m_providers[m_nCurProvider], pNews);
        DBG_NewMem (pItem, "CTickerWindow::PrepareContent, pItem newed.");

		m_lstItem.AddToTail(pItem);
		nCalcWidth = CalcDisplayPos(hdc, nCalcWidth, pItem);
	}
}

int CTickerWindow::CalcDisplayPos (HDC hdc, int nCurScrnPos, CTickerItem* pItem)
{
	SIZE sz;
	
	assert (pItem);
	NewsItem* pNews = pItem->m_pNews;
	assert (pNews);

    if (!GetTextExtentPoint32(hdc, pNews->text, strlen(pNews->text), &sz)) {
        return 0;
    }
	pItem->m_nTextWidth = sz.cx + m_nLeading + m_nTrailing;
	m_nTxtHeight = sz.cy;

	pItem->m_nScreenPos = nCurScrnPos;

	if (nCurScrnPos != 0) {
		// So this is not the first displayed news
		pItem->m_nFirstVisiblePos = 0;
	} else {
		pItem->m_nFirstVisiblePos += m_nMoveStep;
		if (pItem->m_nFirstVisiblePos >= pItem->m_nTextWidth) {
			assert (pItem == m_lstItem.GetHead());
			m_lstItem.RemoveHead();
			return nCurScrnPos;
		}
	}
	nCurScrnPos += (pItem->m_nTextWidth - pItem->m_nFirstVisiblePos);
	return nCurScrnPos;
}

void CTickerWindow::DrawContent(HDC hdc)
{
	UINT nCurPos = 0;
	RECT rc;
	CTickerItem* pItem = m_lstItem.GetHead();

    if (!GetClientRect(&rc)) {
        return;
    }

	while (pItem) {
		DrawTickerItem(hdc, pItem, &rc);
		nCurPos = pItem->m_nScreenPos + pItem->m_nTextWidth - pItem->m_nFirstVisiblePos;
		pItem = pItem->GetNext();
	}
	if ((int)nCurPos < rc.right) {
		rc.left = nCurPos;
		FillRect (hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));		
	}
}

void CTickerWindow::DrawTickerItem(HDC hdc, CTickerItem* pItem, const LPRECT prcClient)
{
	assert (pItem);
	assert (pItem->m_pNews);
	assert (prcClient);

	RECT rcText = *prcClient;
	rcText.left = pItem->m_nScreenPos;
	rcText.right = rcText.left + pItem->m_nTextWidth - pItem->m_nFirstVisiblePos;

    if (!pItem->m_brBk) {
	    pItem->m_brBk = CreateSolidBrush(pItem->m_pNews->bkgrd);
        if (!pItem->m_brBk) {
            return;
        }
    }

    if (FillRect (hdc, &rcText, pItem->m_brBk) == 0) {
        return;
    }

	UINT nTextPos = pItem->m_nScreenPos + m_nLeading - pItem->m_nFirstVisiblePos;
	COLORREF clrOldText = SetTextColor(hdc, pItem->m_pNews->fore);
	COLORREF clrOldBk = SetBkColor(hdc, pItem->m_pNews->bkgrd);
	TextOut (hdc, nTextPos, rcText.bottom/2 - m_nTxtHeight/2, pItem->m_pNews->text, strlen (pItem->m_pNews->text));
	SetTextColor (hdc, clrOldText);
	SetBkColor (hdc, clrOldBk);
}

bool CTickerWindow::CursorInWindow()
{
	POINT pt;
	RECT rcClient;

	::GetCursorPos(&pt);
	ScreenToClient(&pt);
	GetClientRect(&rcClient);
	return ((bool)PtInRect(&rcClient, pt));
}

